package br.mikhas.refractor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * Some utility methods to refractor
 * 
 * @author Mikhail Domanoski
 * 
 */
public class RefractionUtils {

	/**
	 * Gets the types from a list of objects
	 * 
	 * @param objs
	 *            The object list
	 * @return The class of each object on the array
	 */
	public static Class<?>[] getArgumentsTypes(Object[] objs) {
		Class<?>[] types = new Class<?>[objs.length];

		for (int i = 0; i < objs.length; i++) {
			types[i] = objs[i].getClass();
		}

		return types;
	}

	/**
	 * Gets the names of the classes on the array
	 * 
	 * @param cls
	 *            an array of classes
	 * @return a String with the classes names
	 */
	public static String typeNames(Class<?>... cls) {
		StringBuilder builder = new StringBuilder(cls.length * 25);

		for (Class<?> c : cls) {
			builder.append(c.getName()).append(',');
		}
		builder.deleteCharAt(builder.length() - 1);

		return builder.toString();
	}

	/**
	 * Checks if an array of classes is assignable to another array of classes
	 * 
	 * @param source
	 * @param destiny
	 * @return
	 */
	private static boolean assignable(Class<?>[] source, Class<?>[] destiny) {
		if (source.length != destiny.length)
			return false;

		for (int i = 0; i < source.length; i++) {
			if (destiny[i] == null) {
				throw new NullPointerException("null argument type found.");
			}

			if (!source[i].isAssignableFrom(destiny[i])) {
				return false;
			}

		}
		return true;
	}

	/**
	 * Looks for a method that matches with a collection of arguments
	 * 
	 * @param cls
	 *            The class to look up for the method
	 * @param types
	 *            The collection of argument types that the method must match
	 * @return An method that matches with the argument collection
	 */
	public static Method lookupMethod(Class<?> cls, Class<?>[] types) {
		return lookupMethod(cls, null, types);
	}

	/**
	 * Looks for a method that matches with a collection of arguments
	 * 
	 * @param cls
	 *            The class to look up for the method
	 * @param name
	 *            The possible name of the method
	 * @param types
	 *            The collection of argument types that the method must match
	 * @return An method that matches with the argument collection
	 */
	public static Method lookupMethod(Class<?> cls, String name,
			Class<?>[] types) {
		Method m;

		try {
			return cls.getMethod(name, types);
		} catch (Throwable t) {

		}

		Method[] methods = cls.getDeclaredMethods();

		for (int i = 0; i < methods.length; i++) {
			m = methods[i];

			if (name != null && !m.getName().equals(name)) {
				continue;
			}

			Class<?>[] params = m.getParameterTypes();

			if (assignable(params, types)) {
				return m;
			}
		}

		return null;
	}

	/**
	 * Looks for an constructor which accepts the argument list
	 * 
	 * @param <T>
	 *            The type of object which the constructor returns
	 * @param cls
	 *            The class to look for the constructor
	 * @param types
	 *            The type of the arguments to be provided to the constructor
	 * @return The constructor
	 */
	@SuppressWarnings("unchecked")
	public static <T> Constructor<T> lookupConstructor(Class<T> cls,
			Class<?>[] types) {
		Constructor<?> con;

		try {
			return cls.getDeclaredConstructor(types);
		} catch (Throwable t) {

		}

		Constructor<?>[] cons = cls.getDeclaredConstructors();

		for (int i = 0; i < cons.length; i++) {
			con = cons[i];

			Class<?>[] params = con.getParameterTypes();

			if (assignable(params, types)) {
				return (Constructor<T>) con;
			}
		}

		return null;
	}
}
